package TL5 (sysTL, TL) where {

  -- Simple model of a traffic light

  -- Version 5: Add magnetic car-detection sensors and short-cuts
  --            to next demanded green.  If there is no demanded
  --            green, stays in AllRed

  interface TL = { ped_button_push :: Action;
                   set_car_state_N :: Bool -> Action;
                   set_car_state_S :: Bool -> Action;
                   set_car_state_E :: Bool -> Action;
                   set_car_state_W :: Bool -> Action;
                 };

  data TLstates = AllRed
                | GreenNS  | AmberNS
                | GreenE   | AmberE
                | GreenW   | AmberW
                | GreenPed | AmberPed
                deriving (Eq, Bits);

  type Time32 = UInt 5;

  sysTL :: Module TL;
  sysTL =
      module {
          state :: Reg TLstates;
          state <- mkReg AllRed;

          next_green :: Reg TLstates;
          next_green <- mkReg GreenNS;

          secs :: Reg Time32;
          secs <- mkReg 0;

          ped_button_pushed :: Reg Bool;
          ped_button_pushed <- mkReg False;

          car_present_NS :: Reg Bool;
          car_present_NS <- mkReg True;

          car_present_E :: Reg Bool;
          car_present_E <- mkReg True;

          car_present_W :: Reg Bool;
          car_present_W <- mkReg True;

          let {
              allRedDelay :: Time32;
              allRedDelay = 2;

              amberDelay :: Time32;
              amberDelay = 4;

              ns_green_delay :: Time32;
              ns_green_delay = 20;

              ew_green_delay :: Time32;
              ew_green_delay = 10;

              pedGreenDelay :: Time32;
              pedGreenDelay = 10;

              pedAmberDelay :: Time32;
              pedAmberDelay = 6;

              next_state :: TLstates -> Action;
              next_state  ns = action { state := ns; secs := 0; };

              green_seq  :: TLstates -> TLstates;
              green_seq  GreenNS = GreenE;
              green_seq  GreenE  = GreenW;
              green_seq  GreenW  = GreenNS;

              car_present  :: TLstates -> Bool;
              car_present  GreenNS = car_present_NS;
              car_present  GreenE  = car_present_E;
              car_present  GreenW  = car_present_W;
          };
      interface {
          ped_button_push   = ped_button_pushed  := True;
          set_car_state_N b = car_present_NS     := b;
          set_car_state_S b = car_present_NS     := b;
          set_car_state_E b = car_present_E      := b;
          set_car_state_W b = car_present_W      := b;
      };
      addRules $
        rules {
          "wait": when True ==> secs := secs + 1
        }

        +>

        rules {
          "fromAllRed":
            when (state == AllRed) && ((secs + 1) >= allRedDelay)
              ==> if ped_button_pushed then
                      action { ped_button_pushed := False; next_state (GreenPed); }
                  else if car_present (next_green) then
                      next_state (next_green)
                  else if car_present (green_seq next_green) then
                      next_state (green_seq next_green)
                  else if car_present (green_seq (green_seq next_green)) then
                      next_state (green_seq (green_seq next_green))
                  else
                      next_state (AllRed);

          "fromGreenPed":
            when (state == GreenPed) && ((secs + 1) >= pedGreenDelay)
            ==>  next_state (AmberPed);

          "fromAmberPed":
            when (state == AmberPed) && ((secs + 1) >= pedAmberDelay)
            ==>  next_state (AllRed);

          "fromGreenNS":
            when (state == GreenNS) && ((secs + 1) >= ns_green_delay)
            ==>  next_state (AmberNS);

          "fromAmberNS":
            when (state == AmberNS) && ((secs + 1) >= amberDelay)
            ==>  action { next_state (AllRed); next_green := GreenE; };

          "fromGreenE":
            when (state == GreenE) && ((secs + 1) >= ew_green_delay)
            ==>  next_state (AmberE);

          "fromAmberE":
            when (state == AmberE) && ((secs + 1) >= amberDelay)
            ==>  action { next_state (AllRed); next_green := GreenW; };

          "fromGreenW":
            when (state == GreenW) && ((secs + 1) >= ew_green_delay)
            ==>  next_state (AmberW);

          "fromAmberW":
            when (state == AmberW) && ((secs + 1) >= amberDelay)
            ==>  action { next_state (AllRed); next_green := GreenNS; };
        }
      }
}
